package simpledb.transaction;

import simpledb.storage.PageId;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * LockManager 是一个简单的锁管理器，用于管理对数据库页面的锁。
 * 它支持读锁（共享锁）和写锁（排他锁），并提供方法来获取、释放锁以及检查某个事务是否持有某个页面的锁。
 * 通过 synchronized 关键字和 ConcurrentHashMap 保证线程安全
 */
public class LockManager {

    // 存储页面 ID 到锁列表的映射；键 PageId 页面的唯一标识、值 List<Lock> 该页面的所有锁记录
    private final Map<PageId, List<Lock>> lockMap;

    /**
     * 构造函数，初始化锁管理器。
     */
    public LockManager() {
        // 使用线程安全的ConcurrentHashMap，支持高并发的访问
        this.lockMap = new ConcurrentHashMap<>();
    }

    /**
     * 尝试在给定时间内获取指定类型的锁。
     *
     * @param pageId 页面ID
     * @param tid 事务ID
     * @param lockType 锁类型（0: 读锁, 1: 写锁）
     * @param timeout 超时时间（毫秒）
     * @return 是否成功获取锁
     */
    public boolean tryAcquireLock(final PageId pageId, final TransactionId tid, final int lockType, final int timeout) {

        final long now = System.currentTimeMillis(); // 记录开始时间

        while (true) { // 循环尝试获取锁知道超时
            // 检查是否超时
            if (System.currentTimeMillis() - now >= timeout) {
                return false; // 超时未获取到锁
            }
            // 调用无超时的获取锁方法（内部已经实现了同步）
            if (acquireLock(pageId, tid, lockType)) {
                return true; // 成功获取锁
            }
        }
    }

    /**
     * 获取指定类型的锁。核心获取锁的逻辑
     * 没有超时机制，需要配合外层循环实现超时控制
     *
     * @param pageId 页面ID
     * @param tid 事务ID
     * @param lockType 锁类型（0: 读锁, 1: 写锁）
     * @return 是否成功获取锁
     */
    public synchronized boolean acquireLock(final PageId pageId, final TransactionId tid, final int lockType) {

        // 1.如果页面未被锁定，则直接分配锁
        if (!this.lockMap.containsKey(pageId)) {
            final Lock lock = new Lock(tid, lockType);
            final List<Lock> locks = new ArrayList<>();
            locks.add(lock);
            this.lockMap.put(pageId, locks);
            return true;
        }

        // 2.页面已经存在锁，检查当前事务是否持有该锁
        final List<Lock> locks = this.lockMap.get(pageId);

        // 检查当前事务是否已经持有了锁，并且锁类型匹配
        for (final Lock lock : locks) {
            // 如果事务已经持有了锁
            if (lock.getTid().equals(tid)) {
                // 已有锁类型和请求类型保持一致，或已有写锁（写锁兼容读锁）
                if (lock.getLockType() == lockType || lock.getLockType() == 1) {
                    return true;
                }
                // 锁升级：仅当当前是唯一读锁时，允许升级未写锁
                if (lock.getLockType() == 0 && locks.size() == 1) {
                    lock.setLockType(1); // 将读锁升级为写锁
                    return true;
                }
                // 其他情况（如已有读锁但非唯一，无法升级）
                return false;
            }
        }

        // 3.事务未持有锁，检查现有锁是否允许新锁申请
        // 存在写锁时，拒绝所有新锁（写锁互斥）
        if (locks.stream().anyMatch(l -> l.getLockType() == 1)) {
            return false;
        }

        // 4.如果请求的是读锁，可以直接添加到现有锁列表中
        if (lockType == 0) {
            final Lock lock = new Lock(tid, lockType);
            locks.add(lock);
            return true;
        }
        return false;
    }

    /**
     * 释放指定页面上的锁。
     *
     * @param pageId 页面ID
     * @param tid 事务ID
     * @return 是否成功释放锁
     */
    public synchronized boolean releaseLock(final PageId pageId, final TransactionId tid) {
        if (!this.lockMap.containsKey(pageId)) {
            return false;
        }
        final List<Lock> locks = this.lockMap.get(pageId);
        for (int i = 0; i < locks.size(); i++) {
            final Lock lock = locks.get(i);
            if (lock.getTid().equals(tid)) {
                locks.remove(lock);
                if (locks.isEmpty()) {
                    this.lockMap.remove(pageId); // 如果没有其他锁，移除该页面的锁记录
                }
                return true;
            }
        }
        return false;
    }

    /**
     * 通过事务ID释放所有页面上的锁。
     *
     * @param tid 事务ID
     */
    public synchronized void releaseLockByTxn(final TransactionId tid) {
        this.lockMap.forEach((pid, locks) -> {
            if (holdsLock(pid, tid)) {
                releaseLock(pid, tid);
            }
        });
    }

    /**
     * 检查某个事务是否持有指定页面的锁。
     *
     * @param pageId 页面ID
     * @param tid 事务ID
     * @return 是否持有锁
     */
    public synchronized boolean holdsLock(final PageId pageId, final TransactionId tid) {
        if (!this.lockMap.containsKey(pageId)) {
            return false;
        }
        final List<Lock> locks = this.lockMap.get(pageId);
        for (int i = 0; i < locks.size(); i++) {
            final Lock lock = locks.get(i);
            if (lock.getTid().equals(tid)) {
                return true;
            }
        }
        return false;
    }
}
